package cn.dansj.utils.interfaces;

import cn.dansj.annotation.InterfaceParam;
import cn.dansj.annotation.MarkAsApi;
import cn.dansj.bean.CurrentThreadLocalUserInfo;
import cn.dansj.bean.CurrentUser;
import cn.dansj.common.jdbc.mybatis.MybatisXmlParser;
import cn.dansj.common.jdbc.page.PageUtils;
import cn.dansj.common.springboot.reflect.ProxyFactory;
import cn.dansj.common.utils.bean.PageResult;
import cn.dansj.common.utils.json.DictMap;
import cn.dansj.common.utils.enums.ResponseCode;
import cn.dansj.common.jdbc.DBUtils;
import cn.dansj.common.request.RequestUtils;
import cn.dansj.common.redis.logger.RedisAppenderConfigure;
import cn.dansj.common.redis.logger.appender.RedisAppender;
import cn.dansj.common.redis.logger.factory.AppenderFactory;
import cn.dansj.common.redis.logger.factory.LoggerBuilder;
import cn.dansj.common.redis.RedisUtils;
import cn.dansj.common.utils.transfer.*;
import cn.dansj.handler.Interface;
import cn.dansj.bean.InterfaceCheckReturn;
import cn.dansj.bean.InterfaceResponse;
import cn.dansj.entity.InterfaceInfo;
import cn.dansj.service.InterfaceInfoService;
import cn.dansj.common.context.ApplicationContextProvider;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import cn.dansj.common.jdbc.jdbcTemplate.JdbcTemplate;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class InterfaceUtils {
    private final Map<String, Object> requestMap;
    private InterfaceInfo interfaceInfo;
    private InterfaceResponse interfaceResponse;
    private final String url;
    public static final Logger logger;
    private final int limit;
    private final int page;
    private static final JdbcTemplate jdbcTemplate;
    private static final InterfaceInfoService interfaceInfoService;
    private static final RedisUtils redisUtils;
    private final String method;
    private static final ConcurrentMap<String, Interface> interfaceHandlerRepository = new ConcurrentHashMap<>();
    public final static Pattern pattern = Pattern.compile("(!\\{)(.*?)(})");
    public static final Map<String, Object> env = new HashMap<>();

    //静态变量初始化一次
    static {
        jdbcTemplate = ApplicationContextProvider.getBean(JdbcTemplate.class);
        interfaceInfoService = ApplicationContextProvider.getBean(InterfaceInfoService.class);
        redisUtils = ApplicationContextProvider.getBean(RedisUtils.class);

        final RedisAppenderConfigure configure = new RedisAppenderConfigure("interfaceLog").setSplit(RedisAppender.SplitType.day).setJson(false).setBackup(7).setExtend(true);
        logger = LoggerBuilder.getLogger(InterfaceUtils.class, true, AppenderFactory.createRedisAppender(configure));
        logger.info("interface logger init success");
    }

    private InterfaceUtils() {
        final HttpServletRequest httpServletRequest = RequestUtils.getRequest();
        this.requestMap = RequestUtils.getMaybeMultipartRequestParams();
        updateRequestMap();
        this.url = RequestUtils.fromDispatcher();
        this.limit = Transformation.parseInt(requestMap.get("limit"), 10, 1, null);
        this.page = Transformation.parseInt(requestMap.get("page"), 0, 0, null);
        this.method = httpServletRequest.getMethod().toLowerCase();
    }

    public static InterfaceUtils getInstance() {
        return new InterfaceUtils();
    }

    private void updateRequestMap() {
        // 对requestMap更新固定的一些环境变量，强制更新，所以自定义的变量名称不能和这些变量冲突
        final CurrentUser currentUser = CurrentThreadLocalUserInfo.getCurrentThreadLocalUserInfo().getCurrentUser();
        env.put("userId", currentUser.getSequence());
        env.put("userName", currentUser.getUsername());
        env.put("userRole", currentUser.getRole());
        env.put("apiToken", currentUser.getToken());
        requestMap.putAll(env);
    }

    public Object exec() {
        boolean prepareCheck = prepareCheck();
        if (!prepareCheck) {
            return interfaceResponse.setResponseEndTime();
        }

        if ("sql".equals(interfaceInfo.getType())) {
            return runSql().setResponseEndTime();
        } else if ("java".equals(interfaceInfo.getType())) {
            return runJava();
        } else if ("html".equals(interfaceInfo.getType())) {
            return runHtml();
        } else {
            logger.error("接口类型[{}]不存在", interfaceInfo.getType());
            return interfaceResponse.setResponseEndTime().setByInterfaceResponseCode(ResponseCode.TypeNotExist);
        }
    }

    private boolean prepareCheck() {
        InterfaceCheckReturn interfaceCheckReturn = checkGetInterfaceInfo(url, method);
        interfaceResponse = interfaceCheckReturn.getInterfaceResponse();
        interfaceResponse.setCurPage(this.page);//new InterfaceResponse(this.page, this.limit, url);
        interfaceResponse.setPageSize(this.limit);

        //接口不存在时直接返回
        if (interfaceResponse.getResponseCode() == ResponseCode.NotExist.getStatusCode()) return false;

        interfaceInfo = interfaceCheckReturn.getInterfaceInfo();

        if ("sql".equals(interfaceInfo.getType())) {
            //获取到接口信息，开始解析展开引用的接口标签
            parseRefSql(interfaceInfo, interfaceInfoService);
        }

        DictMap params = DictMap.parseObject(interfaceInfo.getParams());

        //注入默认参数及默认值
        for (String p : params.keySet()) {
            Object aDefault = Transformation.nvl(requestMap.get(p), params.getJSONObject(p).getString("default"), "");
            if (params.getJSONObject(p).getBooleanValue("require") && Verification.checkNull(aDefault)) {
                interfaceResponse.setStatus(false).setMessage(String.format(ResponseCode.ParamExistNull.getError(), p)).setResponseCode(ResponseCode.ParamExistNull.getStatusCode());
                return false;
            }
            requestMap.put(p, aDefault);
        }

        interfaceResponse.setVersion(interfaceInfo.getUpdateTime().toString());
        return true;
    }

    private String runHtml() {
        return interfaceInfo.getCode();
    }

    private Object runJava() {
        try {
            //获取缓存中的handler
            Interface interfaceHandler = interfaceHandlerRepository.get(interfaceInfo.getSequence());
            //比对版本信息
            if (interfaceHandler == null || interfaceHandler.getVersion() != interfaceInfo.getVersion()) {
                logger.info("create new handler:{}, version:{} ", interfaceInfo.getInterfaceName(), GetTime.stringTime(interfaceInfo.getUpdateTime()));
                interfaceHandler = InterfaceFactory.loadNewInstance(interfaceInfo.getCode());
                interfaceHandler.setVersion(interfaceInfo.getVersion());
                interfaceHandlerRepository.remove(interfaceInfo.getSequence());
                interfaceHandlerRepository.put(interfaceInfo.getSequence(), interfaceHandler);
            }

            Class<?> realClass = ProxyFactory.getRealClass(interfaceHandler);

            // 运行时参数校验  正常这边不会有问题，在上面prepareCheck中做过检查，这边做二次校验
            Method method = ReflectionUtils.findMethod(realClass, "execute", Map.class);

            assert method != null;
            final InterfaceParam[] interfaceParams = method.getAnnotationsByType(InterfaceParam.class);
            for (InterfaceParam interfaceParam : interfaceParams) {
                if (interfaceParam.require() && Verification.checkNull(requestMap.get(interfaceParam.value()))) {
                    return interfaceResponse.setStatus(false).setMessage(String.format(ResponseCode.ParamExistNull.getError(), interfaceParam.value())).setResponseCode(ResponseCode.ParamExistNull.getStatusCode());
                }
            }

            Object result = interfaceHandler.execute(requestMap);
            final MarkAsApi markAsApi = realClass.getAnnotation(MarkAsApi.class);
            if (markAsApi != null && !markAsApi.nestingInMap()) {
                return Transformation.instanceInt(result) ? result.toString() : result;
            }

            if ("list".equals(interfaceInfo.getReturnType())) {
                if (Verification.checkNull(result)) {
                    result = new ArrayList<>();
                } else if (!(result instanceof List)) {
                    result = ArrayUtils.asList(result);
                }
            } else if ("map".equals(interfaceInfo.getReturnType())) {
                if (Verification.checkNull(result)) {
                    result = new HashMap<>();
                } else if (result instanceof List) {
                    Object data = ((List<?>) result).get(0);
                    result = data instanceof Map ? data : DictMap.of("data", data);
                }
            } else {
                if (Verification.checkNotNull(result) && result instanceof List && ((List<?>) result).size() == 1) {
                    result = ((List<?>) result).get(0);
                }
            }

            InterfaceResponse interfaceResponse = this.interfaceResponse.setStatus(true).setData(result).setSourceCode("5");

            if (result instanceof List) {
                int size = ((List<?>) result).size();
                interfaceResponse.setTotalRecords(size).setCurRecords(size);
            } else {
                interfaceResponse.setTotalRecords(1).setCurRecords(1);
            }
            return interfaceResponse.setResponseEndTime();
        } catch (Exception exception) {
            throw new RuntimeException(exception);
        }
    }

    /**
     * 展开引用其他接口的sql信息
     */
    public static void parseRefSql(InterfaceInfo interfaceInfo, InterfaceInfoService interfaceInfoService) {
        String sqlCode = interfaceInfo.getCode();
        String newSqlCode = sqlCode.replaceAll("<interface ref='(.*?)'/>", "!{$0}");

        Pattern pattern = Pattern.compile("<interface ref='(.*?)'/>");
        Matcher matcher = pattern.matcher(sqlCode);
        DictMap refReplace = DictMap.of();
        DictMap params = DictMap.parseObject(interfaceInfo.getParams());
        while (matcher.find()) {
            String ref = matcher.group(0);
            String refName = matcher.group(1);
            InterfaceInfo refInterfaceInfo = interfaceInfoService.getByName(refName);
            String code = refInterfaceInfo.getCode();
            params.putAll(DictMap.parseObject(refInterfaceInfo.getParams()));
            refReplace.put(ref, code.endsWith(";") ? code : code + ";");
        }

        interfaceInfo.setCode(parse(newSqlCode, refReplace));
        interfaceInfo.setParams(params.toJSONString());
    }

    public static String parse(String text, DictMap map) {
        Matcher m = pattern.matcher(text);
        while (m.find()) {
            //在map中的显示,map中不带 ${}
            String group = m.group(2);
            //在text中的显示,text原始格式带${}
            String inTextGroup = m.group(0);
            String replace = map.getString(group);
            if (replace != null) {
                text = StringUtils.replace(text, inTextGroup, replace);
            }
        }
        return text;
    }

    private String checkDDL(List<String> sqlList) {
        for (String sql : sqlList) {
            if (!DBUtils.isDQL(sql) && !DBUtils.isCall(sql)) {
                return sql;
            }
        }
        return null;
    }

    public static InterfaceCheckReturn checkGetInterfaceInfo(String url, String method) {
        method = method.toLowerCase();
        InterfaceResponse interfaceResponse = new InterfaceResponse(url).setMessage("查询成功");
        InterfaceInfo interfaceInfo = null;
        interfaceResponse.setVersion("-1");
        //根据路径查询对应接口信息
        try {
            //根据名称忽略sequence模糊查询,正常应该返回一条记录,返回多条说明没有删干净,全部删除,抛出异常从数据库中重新获取
            List<String> keys = redisUtils.keys(String.format("Interface@%s@*", Md5Utils.md5(url + method)));
            if (keys.size() > 1) {
                logger.info("interface:{}, 出现多条匹配结果,即将全部删除->{}", url, keys);
                for (String key : keys) {
                    redisUtils.delete(key);
                }
                throw new IllegalArgumentException();
            } else {
                DictMap data = DictMap.from(redisUtils.hgetall(keys.get(0)));
                if (ResponseCode.NotExist.getError().equals(data.getString("message"))) {
                    logger.info("interface:{} not exists", url);
                    interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.NotExist);
                } else {
                    interfaceInfo = DictMap.parseObject(data, InterfaceInfo.class);
                    logger.debug("interface:{} get from cache", url);
                }

            }
        } catch (Exception e) {
            logger.debug("interface:{} get from database", url);

            try {
                interfaceInfo = interfaceInfoService.getInterface(url, 1, method);
            } catch (Exception exception) {
                logger.error("获取接口异常:{}", Transformation.getBaseException(exception).getMessage());
                interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.Error);
            }

            //将数据库的查询结果写到redis中
            if (interfaceInfo != null) {
                redisUtils.hmset(String.format("Interface@%s@%s", Md5Utils.md5(url + method), interfaceInfo.getSequence()), interfaceInfo.toJSONObject());
            } else {
                boolean hmset = redisUtils.hmset(String.format("Interface@%s@%s", Md5Utils.md5(url + method), "-1"), DictMap.of("message", ResponseCode.NotExist.getError(), "url", url, "method", method));
                logger.debug("interface:{} not exists, write to redis:{}", url, hmset);
                interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.NotExist);
            }
        }
        return new InterfaceCheckReturn(interfaceInfo, interfaceResponse);
    }

    private InterfaceResponse runSql() {
        //获取sql
        String sqlCode = interfaceInfo.getCode();

        List<String> sqlList = ArrayUtils.asList(StringUtils.split(sqlCode, ";"));

        sqlList.removeIf(Verification::checkNull);

        String ddlSql = checkDDL(sqlList);

        if (Verification.checkNotNull(ddlSql)) {
            return interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.InterceptDDL);
        }

        int index = 0;
        String sql = "";
        while (index < sqlList.size()) {
            sql = DBUtils.removeComments(sqlList.get(index).trim());

            try {
                if (!ArrayUtils.asList(sql.split("[ \n]")).contains("call")) {
                    sql = DBUtils.formatSql(getParsedSql(sql, requestMap));
                    //校验sql中是否存在未解析变量, 正常情况不应该存在
                    logger.debug("interface:{}, 校验未解析变量 ", url);
                    List<String> unparsed = Transformation.checkUnParsedVariable(sql);
                    if (!unparsed.isEmpty()) {
                        return interfaceResponse.setStatus(false).setMessage(String.format(ResponseCode.ParamExistNull.getError(), String.join(",", unparsed))).setResponseCode(ResponseCode.ParamExistNull.getStatusCode());
                    }
                }
            } catch (Exception exception) {
                logger.error("exceptionMsg:{}, sql解析失败:{},params:{}", Transformation.getBaseException(exception).getMessage(), sql, requestMap);
                return interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.InterceptSQLParseError);
            }

            //处理前面所有的生成变量的逻辑, 最后一个生成数据的不在这里处理
            if (index < sqlList.size() - 1) {
                try {
                    Map<String, ?> map;
                    if (ArrayUtils.asList(sql.split("[ \n]")).contains("call")) {
                        CallStatement.ParseOutput parseOutput = CallStatement.parseMybatisParams(sql, requestMap);
                        sql = parseOutput.sql();
                        map = CallStatement.exec(parseOutput);
                    } else {
                        map = jdbcTemplate.queryForMap(sql);
                    }

                    interfaceResponse.setSourceCode(sql);
                    requestMap.putAll(map);
                    logger.debug("interface:{}, 自解析SQL:{}, 生成后新的变量{}", url, sql, map);
                } catch (IncorrectResultSizeDataAccessException exception) {
                    logger.error("sql:{}, 结果不唯一, 存在{}条记录, 处理失败", sql, exception.getActualSize());
                    return interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.ResultNotUnique);
                } catch (Exception exception) {
                    logger.error("exceptionMsg:{}， 自解析sql-{}执行失败:{},params:{}", Transformation.getBaseException(exception).getMessage(), index + 1, sql, requestMap);
                    return interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.SqlRunFailed);
                }
            }
            index++;
        }

        //存储过程回调方式不允许分页
        if (ArrayUtils.asList(sql.split("[ \n]")).contains("call")) {
            logger.debug("{}call回调无法开启分页,请在存储过程中处理分页", url);

            CallStatement.ParseOutput parseOutput = CallStatement.parseMybatisParams(sql, requestMap);

            logger.debug("interface:{}, querySql:{}", url, DBUtils.formatSql(parseOutput.sql()));
            interfaceResponse.setSourceCode(DBUtils.formatSql(parseOutput.sql()));

            Map<String, List<Map<String, Object>>> result = CallStatement.exec(parseOutput);
            int size = 0;
            for (String key : result.keySet()) {
                size += result.get(key).size();
            }

            interfaceResponse.setByInterfaceResponseCode(ResponseCode.Success).setStatus(true).setCurRecords(size).setData(result).setTotalRecords(size).setTotalPage(-1);
            return interfaceResponse;
        }

        //开始处理生成数据的逻辑
        //判断page参数,大于0则需要分页
        if (page > 0) {
            logger.debug("{}开启分页", url);
            try {
                PageUtils.startPage();
                final PageUtils pageUtils = PageUtils.getInstance(jdbcTemplate);
                final PageResult<Object> pageResult = pageUtils.getPageData(sql);
                this.interfaceResponse.setSourceCode(DBUtils.formatSql(sql));
                this.interfaceResponse.setTotalPage(pageResult.getTotalPages());
                this.interfaceResponse.setCurRecords(pageResult.getCurrentSize());
                this.interfaceResponse.setTotalRecords(pageResult.getTotalSize());
                this.interfaceResponse.setResponseEndTime(pageResult.getEndTime().stringTimestamp());
                this.interfaceResponse.setData(pageResult.getData());
                logger.debug("interface=>{}, querySql=>{}", url, DBUtils.formatSql(interfaceResponse.getSourceCode()));
                if (!pageResult.isStatus()) {
                    this.interfaceResponse.setByInterfaceResponseCode(ResponseCode.Error);
                }
            } catch (Exception e) {
                logger.error("查询异常:{}", Transformation.getBaseException(e).getMessage());
                interfaceResponse.setStatus(false).setByInterfaceResponseCode(ResponseCode.Error);
            }
        } else {
            logger.debug("{}未开启分页", url);
            logger.debug("interface:{}, querySql:{}", url, DBUtils.formatSql(sql));
            interfaceResponse.setSourceCode(DBUtils.formatSql(sql));
            //List<Map<String, Object>> data = jdbcTemplate.queryForList(sql);
            FinalResult data = getFinalResult(sql, interfaceInfo.getReturnType());
            interfaceResponse.setByInterfaceResponseCode(ResponseCode.Success).setStatus(true).setCurRecords(data.getSize()).setData(data.getResult()).setTotalRecords(data.getSize()).setTotalPage(-1);
        }

        return interfaceResponse;
    }

    private FinalResult getFinalResult(String sql, String returnType) {
        logger.debug("interface:{}, returnType:{}", url, returnType);
        if ("list".equalsIgnoreCase(returnType)) {
            List<Map<String, Object>> query = jdbcTemplate.queryForList(sql);
            return new FinalResult(query.size(), query);
        } else if ("map".equalsIgnoreCase(returnType)) {
            return new FinalResult(1, jdbcTemplate.queryForMap(sql));
        } else {
            List<Map<String, Object>> query = jdbcTemplate.queryForList(sql);
            if (query.size() == 1) {
                return new FinalResult(1, query.iterator().next());
            } else if (query.size() > 1) {
                return new FinalResult(query.size(), query);
            } else {
                return new FinalResult(0, null);
            }
        }
    }

    private static String getParsedSql(String sql, Map<?, ?> requestMap) throws SQLException {
        if (Transformation.patternGets(sql, "<!\\[CDATA\\[.*?]]>").isEmpty() && Transformation.patternGets(sql, "<.*?>.*?</.*?>", null, Pattern.DOTALL).isEmpty()) {
            sql = "<![CDATA[ " + sql + " ]]>";
        }

        MybatisXmlParser parser = new MybatisXmlParser();
        return parser.parseSql(jdbcTemplate, sql, requestMap);
    }

    private record FinalResult(int size, Object result) {
        public int getSize() {
            return size;
        }

        public Object getResult() {
            return result;
        }
    }
}
